package cn.bugstack.middleware.db.router;

import cn.bugstack.middleware.db.router.annotation.DBRouter;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author wcc
 * @description 处理器 数据库路由计算核心类
 * @create
 */
@Aspect
@Component("db-router-point")
public class DBRouterJoinPoint {
    private Logger logger = LoggerFactory.getLogger(DBRouterJoinPoint.class);

    @Autowired
    private DBRouterConfig dbRouterConfig;

    @Pointcut("@annotation(cn.bugstack.middleware.db.router.annotation.DBRouter)")
    public void aopPoint() {
    }
    @Around("aopPoint() && @annotation(dbRouter)")
    public Object doRouter(ProceedingJoinPoint jp, DBRouter dbRouter) throws NoSuchMethodException {
        //"userId"
        String dbKey = dbRouter.key();
        if (StringUtils.isBlank(dbKey)) throw new RuntimeException("数据库路由KEY不能为空");
        //计算路由  参数user的userId
        String dbKeyAttr = getAttrValue(dbKey,jp.getArgs());
        //总表数
        int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();
        //扰动函数
        //通过userId获取库表 保证同一个userId映射到同一张表
        int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));
        // 库表索引
        int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
        int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);
        //设置到ThreadLocal
        //设置动态数据源 比如这里结果获得 dbKey=01  tbKey=01
        //determineCurrentLookupKey()返回 db01
        //spring从dataSourceMap中获取 key为db01的那套数据源
        DBContextHolder.setDBKey(String.format("%02d",dbIdx));
        //mybatis通过${}读取threadLocal中的tbKey  "FROM user_${tbIdx}"  最终获得user_01
        DBContextHolder.setTBKey(String.format("%02d",tbIdx));
        logger.info("数据库路由 method: {} dbIx: {}, tbIdx: {}", getMethod(jp).getName(), dbIdx, tbIdx);
        //返回结果
        try{
            return jp.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            DBContextHolder.clearDBKey();
            DBContextHolder.clearTBKey();
        }
    }

    private Method getMethod(ProceedingJoinPoint jp) throws NoSuchMethodException {
        Signature signature = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    private String getAttrValue(String attr, Object[] args){
      String filedValue = null;
      for (Object arg : args){
          try{
              if (StringUtils.isNotBlank(filedValue)) break;
              //获取arg中名为attr的值
              filedValue = BeanUtils.getProperty(arg,attr);
          }catch (Exception e){
              logger.error("获取路由属性失败，attr:{}",attr,e);
          }
      }
      return filedValue;
    }


}
